using System;
using System.IO;
using System.Reflection;
using System.Text;
using System.Xml;
using System.Xml.XPath;
using System.Xml.Xsl;
using NUnit.Framework;

namespace PaulRz.Util
{
	/// <summary>
	/// Merges two <see cref="XmlNode"/> objects.
	/// </summary>
	public sealed class XmlMerge
	{
		const string First = "first";
		const string Second = "second";
		static XslTransform Transformation;
		static IXPathNavigable Input;

		static XmlMerge()
		{
			Transformation = new XslTransform();
			Transformation.Load( new XmlTextReader( 
				Assembly.GetExecutingAssembly().GetManifestResourceStream(
				"PaulRz.Util.merge.xslt") ), 
				null, null);

			// Dummy input document for the merge stylesheet.
			Input = new XPathDocument( new StringReader ( String.Format( 
				@"<?xml version='1.0'?>
      <merge xmlns='http://informatik.hu-berlin.de/merge'>
        <file1>mem://{0}</file1>
        <file2>mem://{1}</file2>
      </merge>", First, Second ) ) );
		}

		public static string Merge( string first, string second, bool replace )
		{
			return Merge(new XPathDocument(first),new XPathDocument(second),replace);
		}

		private XmlMerge() {}

		/// <summary>
		/// Merges the first XML with the second.
		/// </summary>
		/// <param name="first">First XML.</param>
		/// <param name="second">Second XML.</param>
		/// <param name="replace">If set to <see langword="true"/> replaces 
		/// text values from <paramref name="first"/> with the ones in 
		/// <paramref name="second"/> if nodes are equal.</param>
		/// <returns>The merged XML.</returns>
		public static string Merge( IXPathNavigable first, IXPathNavigable second, bool replace )
		{
			// Holds the merged results.
			StringBuilder sb = new StringBuilder();
			XmlTextWriter tw = new XmlTextWriter(new StringWriter(sb));
			tw.Formatting = Formatting.None;

			// Specify whether second node replaces text from first one.
			XsltArgumentList args = new XsltArgumentList();
			args.AddParam("replace", String.Empty, replace);

			Transformation.Transform(Input, args, tw, new XmlNodeResolver(first, second));
			tw.Flush();
			return sb.ToString();
		}

		/// <summary>
		/// Resolves the dummy URL locations to the parameters received.
		/// </summary>
		private class XmlNodeResolver : XmlResolver
		{
			IXPathNavigable _first;
			IXPathNavigable _second;

			public XmlNodeResolver(IXPathNavigable first, IXPathNavigable second)
			{
				_first = first;
				_second = second;
			}

			public override object GetEntity(Uri absoluteUri, string role, Type ofObjectToReturn)
			{
				if ( absoluteUri.Authority == First )
					return _first.CreateNavigator();
				else if ( absoluteUri.Authority == Second )
					return _second.CreateNavigator();
				return null;
			}

			public override System.Net.ICredentials Credentials 
			{ 
				set {} 
			}
		}
	}

	[TestFixture]
	public class TestMerge
	{
		public TestMerge()
		{
		}

		[Test]
		public void Test()
		{  
			XmlDocument first = new XmlDocument();
			XmlDocument second = new XmlDocument();

			string str1 = "<themes><theme id=\"appl\"><title xml:lang=\"nl\">Toepassingen</title></theme></themes>";
			string str2 = "<themes><theme id=\"doc\" /><theme id=\"appl\"><title xml:lang=\"en\">Applications</title></theme></themes>";
			string str3 = "<themes><theme id=\"doc\" /><theme id=\"appl\"><title xml:lang=\"nl\">Toepassingen</title><title xml:lang=\"en\">Applications</title></theme></themes>";

			first.LoadXml(str1);
			second.LoadXml(str2);

			Assert.AreEqual(XmlMerge.Merge(first, second, true), str3);

			FileInfo fi1 = new FileInfo("C:\\file1.xml");
			FileInfo fi2 = new FileInfo("C:\\file2.xml");
			FUtil.setFileContent(fi1.FullName, str1);
			FUtil.setFileContent(fi2.FullName, str2);
			Assert.AreEqual(XmlMerge.Merge(fi1.FullName, fi2.FullName, true), str3);
			fi1.Delete();
			fi2.Delete();
		}
	}
}
